package com.zhu.rtda.heap.constant;

import com.zhu.classfile.constant.base.ConstantMethodrefInfo;
import com.zhu.rtda.heap.InstanceKlass;
import com.zhu.rtda.heap.Method;

/**
 * 方法符号引用（对应 Go 的 MethodRef）
 *
 * 用于表示对一个方法的引用。
 * 在 class 文件中，由 CONSTANT_Methodref_info 表示。
 */
public class MethodRef extends MemberRef {

    /**
     * 解析后的方法对象（直接引用）
     */
    private Method method;

    /**
     * 从 class 文件的 CONSTANT_Methodref_info 创建方法符号引用
     *
     * @param cp      运行时常量池
     * @param refInfo class 文件中的 CONSTANT_Methodref_info
     * @return 新建的方法符号引用
     */
    //对于一个方法来说它总是有类 名字 类型,参数列表（描述符）需要缓存字符串
    public static MethodRef newMethodRef(RuntimeConstantPool cp, ConstantMethodrefInfo refInfo) {
        MethodRef ref = new MethodRef();
        ref.runtimeConstantPool = cp;
        ref.copyMemberRefInfo(refInfo);
        return ref;
    }

    /**
     * 获取解析后的方法对象
     */
    public Method resolvedMethod() {
        if (method == null) {
            resolveMethodRef();
        }
        return method;
    }

    /**
     * 解析方法符号引用
     */
    private void resolveMethodRef() {
        // 1. 获取当前使用该符号引用的类（用于访问控制检查）
        InstanceKlass d = this.runtimeConstantPool.getInstanceKlass();

        // 2. 解析方法所属的类（classIndex -> InstanceKlass）
        InstanceKlass c = this.resolvedKlass();

        // 3. 检查：c 必须是非接口（否则 IncompatibleClassChangeError）
        if (c.isInterface()) {
            throw new IncompatibleClassChangeError();
        }

        // 4. 在 c 及其父类中查找方法（绝不查接口！）
        Method method = lookupMethodInClass(c, this.name, this.descriptor);
        if (method == null) {
            throw new NoSuchMethodError(this.name + this.descriptor);
        }

        // 5. 访问权限检查（d 能否访问 method）
        if (!method.isAccessibleTo(d)) {
            throw new IllegalAccessError();
        }

        this.method = method;
    }

    /**
     * 在类及其父类中查找方法（不包括接口）
     */
    private static Method lookupMethodInClass(InstanceKlass klass, String name, String descriptor) {
        for (InstanceKlass c = klass; c != null; c = c.getSuperClass()) {
            for (Method method : c.getMethods()) {
                if (method.getName().equals(name) && method.getDescriptor().equals(descriptor)) {
                    return method;
                }
            }
        }
        return null;
    }

}
